
#include <BReadWrite>
#include <BImage>
#include "member_BImage.h"
#include "member_BTexture.h"

using namespace BWE;

#define member					(*(member_BImage*)_ptr)
#define member_allocate()		_ptr = new member_BImage(this)
#define member_release()		delete (member_BImage*)_ptr

BImage::BImage()
{
	member_allocate();
}
BImage::BImage(const BString& fileName)
{
	member_allocate();
	if (ReadFile(fileName, this))
	{
		member.fileName = fileName;
	}
}
BImage::BImage(Format format)
{
	member_allocate();
	setFormat(format);
}
BImage::BImage(const BSize& size, Format format)
{
	member_allocate();
	setFormat(format);
	setSize(size);
}
BImage::BImage(int width, int height, Format format)
{
	member_allocate();
	setFormat(format);
	setSize(width, height);
}
BImage::BImage(const BImage& other)
{
	member_allocate();
	setBlend(other.blend());
	setFilter(other.filter());
	setWrap(other.wrap());
	setPolicy(other.policy());

	setFormat(other.format());
	setSize(other.size());
	setPixels(other.pixels());
}
BImage::~BImage()
{
	member_release();
}

void BImage::setFileName(const BString& fileName)
{
	if (member.fileName != fileName)
	{
		member.fileName = fileName;
		emit(Signal_Changed);
	}
}
const BString& BImage::fileName() const
{
	return member.fileName;
}

void BImage::setPolicy(Policy policy)
{
	if (member.policy != policy)
	{
		member.policy = policy;
		emit(Signal_Changed);
	}
}
Policy BImage::policy() const
{
	return member.policy;
}

void BImage::setFormat(Format format)
{
	if (member.format != format)
	{
		member.format = format;
		switch (format)
		{
		case Format_RGB:
			member.fmtlen = 3;
			break;
		case Format_RGBA:
			member.fmtlen = 4;
			break;
		case Format_Alpha:
			member.fmtlen = 1;
			break;
		case Format_Gray:
			member.fmtlen = 1;
			break;
		case Format_Gray_Alpha:
			member.fmtlen = 2;
			break;
		case Format_Char:
			member.fmtlen = 1;
			break;
		case Format_Short:
			member.fmtlen = 2;
			break;
		case Format_UChar:
			member.fmtlen = 1;
			break;
		case Format_UShort:
			member.fmtlen = 2;
			break;
		case Format_Int:
		case Format_UInt:
		case Format_Float:
			member.fmtlen = 4;
			break;
		case Format_Double:
			member.fmtlen = 8;
			break;
		default:
			member.format = Format_None;
			member.fmtlen = 0;
			break;
		}
		member.resizePixels();
		this->dirty();
	}
}
Format BImage::format() const
{
	return member.format;
}

void BImage::setSize(int width, int height)
{
	setSize(BSize(width, height));
}
void BImage::setSize(const BSize& size)
{
	if(member.size != size)
	{
		member.size = size;
		member.resizePixels();
		this->dirty();
	}
}
const BSize& BImage::size() const
{
	return member.size;
}

void BImage::setWidth(int width)
{
	BSize size(width, member.size.height());
	setSize(size);
}
int BImage::width() const
{
	return member.size.width();
}

void BImage::setHeight(int height)
{
	BSize size(member.size.width(), height);
	setSize(size);
}
int BImage::height() const
{
	return member.size.height();
}

void BImage::setPixels(BByteArray* pixels)
{
	if (member.pixels != pixels)
	{
		member.pixels = pixels;
		this->dirty();
	}
}
void BImage::setPixels(const BByteArray* pixels)
{
	if (member.pixels->size() == pixels->size())
	{
		(*member.pixels) = *pixels;
		this->dirty();
	}
}
BByteArray* BImage::pixels()
{
	return member.pixels;
}
const BByteArray* BImage::pixels() const
{
	return member.pixels;
}

BByte* BImage::bytes(int row)
{
	if (member.pixels.empty())
		return 0;
	BByte* data = member.pixels->data();
	return data + member.fmtlen * member.size.width() * row;
}
const BByte* BImage::bytes(int row) const
{
	if (member.pixels.empty())
		return 0;
	const BByte* data = member.pixels->data();
	return data + member.fmtlen * member.size.width() * row;
}

bool BImage::setPixelColor(int x, int y, const BColor& color)
{
	if (member.pixels.empty())
		return false;
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	BByte* data = member.pixels->data() + member.fmtlen * (member.size.width() * y + x);
	switch (member.format)
	{
	case Format_RGBA:
		data[0] = color.r();
		data[1] = color.g();
		data[2] = color.b();
		data[3] = color.a();
		break;
	case Format_RGB:
		data[0] = color.r();
		data[1] = color.g();
		data[2] = color.b();
		break;
	case Format_Alpha:
		data[0] = color.a();
		break;
	case Format_Gray:
		data[0] = color.gray();
		break;
	case Format_Gray_Alpha:
		data[0] = color.gray();
		data[1] = color.a();
		break;
	default:
		return false;
	}
	return true;
}
bool BImage::pixelColor(int x, int y, BColor& color) const
{
	if (member.pixels.empty())
		return false;
	if (x < 0 || x >= member.size.width())
		return false;
	if (y < 0 || y >= member.size.height())
		return false;
	const BByte* data = member.pixels->data() + member.fmtlen * (member.size.width() * y + x);
	switch (member.format)
	{
	case Format_RGBA:
		color.r() = data[0];
		color.g() = data[1];
		color.b() = data[2];
		color.a() = data[3];
		break;
	case Format_RGB:
		color.r() = data[0];
		color.g() = data[1];
		color.b() = data[2];
		break;
	case Format_Alpha:
		color.a() = data[0];
		break;
	case Format_Gray:
		color.r() = data[0];
		color.g() = data[0];
		color.b() = data[0];
		break;
	case Format_Gray_Alpha:
		color.r() = data[0];
		color.g() = data[0];
		color.b() = data[0];
		color.a() = data[1];
		break;
	default:
		return false;
	}
	return true;
}
BColor BImage::pixelColor(int x, int y) const
{
	BColor color;
	pixelColor(x, y, color);
	return color;
}

void BImage::clear()
{
	member.format = Format_None;
	member.size.set(0, 0);
	member.pixels = 0;
	this->dirty();
}
bool BImage::empty() const
{
	if(member.size.width() * member.size.height() <= 0)
		return true;
	if(member.format == Format_None)
		return true;
	if(member.pixels == 0)
		return true;
	return false;
}
void BImage::dirty()
{
	texture_member(this)->dirty = true;
	member.changed = true;
	emit(Signal_Dirty);
}

bool BImage::inverse(bool alpha)
{
	if (member.pixels.empty())
		return false;
	int fmtlen = member.fmtlen;
	BByte* data = member.pixels->data();
	int size = member.pixels->size();
	if (member.format == Format_RGBA)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			bytes[1] = 255 - bytes[1];
			bytes[2] = 255 - bytes[2];
			if (alpha)
				bytes[3] = 255 - bytes[3];
			pos += 4;
		}
		return true;
	}
	if (member.format == Format_RGB)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			bytes[1] = 255 - bytes[1];
			bytes[2] = 255 - bytes[2];
			pos += 3;
		}
		return true;
	}
	if (member.format == Format_Gray)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			pos += 1;
		}
		return true;
	}
	if (member.format == Format_Gray && alpha)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			pos += 1;
		}
		return true;
	}
	if (member.format == Format_Gray_Alpha)
	{
		int pos = 0;
		while (pos < size)
		{
			BByte* bytes = data + pos;
			bytes[0] = 255 - bytes[0];
			if (alpha)
				bytes[1] = 255 - bytes[1];
			pos += 2;
		}
		return true;
	}
	return false;
}
bool BImage::reverse(Orientation orientation)
{
	if (member.pixels.empty())
		return false;
	BByte* data = member.pixels->data();
	int rowlen = member.fmtlen * member.size.width();
	int fmtlen = member.fmtlen;
	bool changed = false;
	if (orientation & Orientation_Vertical)
	{
		int r0 = 0;
		int r1 = member.size.height() - 1;
		while (r0 < r1)
		{
			BByte* ptr0 = data + r0 * rowlen;
			BByte* ptr1 = data + r1 * rowlen;
			bSwap(ptr0, ptr1, rowlen);
			r0++;
			r1--;
		}
		changed = true;
	}
	if (orientation & Orientation_Horizontal)
	{
		for(int r = 0; r < member.size.height(); r++)
		{
			BByte* ptr = data + r * rowlen;
			int c0 = 0;
			int c1 = member.size.width() - 1;
			while (c0 < c1)
			{
				bSwap(ptr + c0 * fmtlen, ptr + c1 * fmtlen, fmtlen);
				c0++;
				c1--;
			}
		}
		changed = true;
	}
	if (changed)
		this->dirty();
	return true;
}

bool BImage::flush() const
{
	BTexture::flush();

	if (texture_member(this)->tbo == 0)
		return false;

	GLuint tbo = texture_member(this)->tbo;

	if (member.changed && member.pixels)
	{
		int size = member.pixels->size();
		int w = member.size.width();
		int h = member.size.height();

		if (size != w * h * member.fmtlen)
			return false;

		GLvoid* data = (GLvoid*)member.pixels->data();

		glBindTexture(GL_TEXTURE_2D, tbo);
		switch (member.format)
		{
		case Format_Alpha:
		case Format_UChar:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Short:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_SHORT, data);
			break;
		case Format_UShort:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_SHORT, data);
			break;
		case Format_Int:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_INT, data);
			break;
		case Format_UInt:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_INT, data);
			break;
		case Format_RGBA:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_RGB:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Gray:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Gray_Alpha:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
			break;
		case Format_Compressed_Alpha:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_ALPHA, w, h, 0, size, data);
			break;
		case Format_Compressed_RGBA:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA, w, h, 0, size, data);
			break;
		case Format_Compressed_RGB:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, w, h, 0, size, data);
			break;
		case Format_Compressed_Gray:
			glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_LUMINANCE, w, h, 0, size, data);
			break;
		default:
			break;
		}
		if (member.policy == Policy_Fixed)
			member.pixels = 0;
		member.changed = false;
	}
	glBindTexture(GL_TEXTURE_2D, tbo);
	return true;
}

const BImage& BImage::operator = (const BImage& other)
{
	setBlend(other.blend());
	setFilter(other.filter());
	setWrap(other.wrap());
	setPolicy(other.policy());

	setFormat(other.format());
	setSize(other.size());
	setPixels(other.pixels());

	return *this;
}
